Se ejecutan los paquetes necesarios:
- readxl: Para leer archivos de datos
- rpart: Para ajustar modelos de árboles de decisión
- randomForest: Para ejecutar modelos de Random Forest
- gbm: Para ejecutar Boosting de árboles
- caret: Para entrenar modelos
- psych: Para una mejor visualización de las estadisticas descriptivas
- plotly: Para generar graficos
library(readxl)
library(rpart)
library(rpart.plot)
library(ipred)
library(caret)
library(randomForest)
library(gbm)
library(plotly)
library(psych)
library(reshape2)
library(imager)
library(data.table)
Metodologia de analisis
Como primer paso se debe importar los datos a la herramienta de nuestra preferencia, en este caso R, con el fin de familiarizarse con ellos y realizar un analisis exploratorio inicial
HR <- read_excel("C:/Users/n.mejia10/Desktop/Prueba/WA_Fn-UseC_-HR-Employee-Attrition.xlsx") ##Se importan los datos
Tratamiento de variables
Es necesario especificar al programa la naturaleza de cada una de las variables con el fin de no hacr analisis erroneos. Por esta razon variables presentes en la base de datos como: “Education”, “Attrition”, “Gender” se transforman en variables categoricas o factores.
Factores<-c("BusinessTravel", "Education", "EducationField", "EnvironmentSatisfaction", "Gender", "JobInvolvement", "JobLevel", "JobRole", "JobSatisfaction", "MaritalStatus", "Over18", "OverTime", "PerformanceRating", "RelationshipSatisfaction", "StockOptionLevel", "WorkLifeBalance", "Attrition", "Department")
HR[Factores]<- lapply(HR[Factores],factor)
HR<-HR[,-c(9:10, 22, 27)]
Analisis exploratorio inicial
Se seleccionan unicamente las variables numericas y se obtienen estadisticas descriptivas con el fin de ver sus tendencias y distribuciones.
ED<-HR %>% select(which(sapply(., is.numeric)))
describe(ED)
vars n mean sd median trimmed mad min
Age 1 1470 36.92 9.14 36.0 36.47 8.90 18
DailyRate 2 1470 802.49 403.51 802.0 803.83 510.01 102
DistanceFromHome 3 1470 9.19 8.11 7.0 8.08 7.41 1
HourlyRate 4 1470 65.89 20.33 66.0 66.02 26.69 30
MonthlyIncome 5 1470 6502.93 4707.96 4919.0 5667.24 3260.24 1009
MonthlyRate 6 1470 14313.10 7117.79 14235.5 14286.48 9201.76 2094
NumCompaniesWorked 7 1470 2.69 2.50 2.0 2.36 1.48 0
PercentSalaryHike 8 1470 15.21 3.66 14.0 14.80 2.97 11
TotalWorkingYears 9 1470 11.28 7.78 10.0 10.37 5.93 0
TrainingTimesLastYear 10 1470 2.80 1.29 3.0 2.72 1.48 0
YearsAtCompany 11 1470 7.01 6.13 5.0 5.99 4.45 0
YearsInCurrentRole 12 1470 4.23 3.62 3.0 3.85 4.45 0
YearsSinceLastPromotion 13 1470 2.19 3.22 1.0 1.48 1.48 0
YearsWithCurrManager 14 1470 4.12 3.57 3.0 3.77 4.45 0
max range skew kurtosis se
Age 60 42 0.41 -0.41 0.24
DailyRate 1499 1397 0.00 -1.21 10.52
DistanceFromHome 29 28 0.96 -0.23 0.21
HourlyRate 100 70 -0.03 -1.20 0.53
MonthlyIncome 19999 18990 1.37 0.99 122.79
MonthlyRate 26999 24905 0.02 -1.22 185.65
NumCompaniesWorked 9 9 1.02 0.00 0.07
PercentSalaryHike 25 14 0.82 -0.31 0.10
TotalWorkingYears 40 40 1.11 0.91 0.20
TrainingTimesLastYear 6 6 0.55 0.48 0.03
YearsAtCompany 40 40 1.76 3.91 0.16
YearsInCurrentRole 18 18 0.92 0.47 0.09
YearsSinceLastPromotion 15 15 1.98 3.59 0.08
YearsWithCurrManager 17 17 0.83 0.16 0.09
Se puede observar que el empleado medio de la compañia tiene 36 años, gana 802 dolares diarios, ha recibido un promedio de 3 capacitaciones el año pasado, tiene 2.1 años desde su ultima promocion y lleva 7 años en la compañia.
Modelos
Una vez se realizó un analisis exploratorio inicial, se procede a responder la pregunta de interes: ¿Cuales son las variables que mas influyen en la satisfacción con respecto al trabajo de los empleados de la empresa. Para responder la pregunta se entrenaran modelos basados en árboles con el fi nde predecir la variable de satisfacción.
Esto debido a la ventaja que poseen los árboles en que permiten, despues de ejecutado el modelo, cuantificar la importancia de cada variable en la construccion del mismo. Esto mediante el coeficiente de pureza o gini.
En este sentido se ajustaran 3 modelos basado en árboles: Un RandomForest, Un Bagging y un Boosting con el fin de encontrar para cada uno las variables ams relevenate y comparar los resultados.
Para realizar los modelos, se sepran los datos en 2 sets: Uno de Entrenamiento y otro de Test
set.seed(123)
ID<-sample(1:nrow(HR),size=nrow(HR)*0.7)
train<-HR[ID,] #Selecciona el 70% de las filas
test<-HR[-ID,] #Selecciona el 30% restante de filas
CART
Se realiza un árbol de clasificación em primer lugar para aprovechar de su estructura grafica y empezar con una idea de las vara¿iables por las cuales parte en las ramas superiores. Puede observarse que variables de slario y tipo de trabajo resultan las mas importantes en la construcción del árbol.
## Se ajusta un CART con los parametro base
set.seed(123)
tree <- rpart(JobSatisfaction ~ ., data = train, method="class", control=rpart.control(xval=10, cp=1e-04))
prp(tree)

x<-varImp(tree)
setorder(x,Overall)
x
Bagging
Como siguiente paso se implementa un Bagging y se busca la importancia de las variables. Baggin se implementa con la libreria RandomForest debido a que es un caso especial de RF donde se utilizan todas las variables en cada nuevo árbol. En este caso, resultan importantes las variables de salario, seguido por la edad y la distancia que debe recorrer de la casa al trabajo.
Bag <- randomForest(JobSatisfaction ~ ., data = train, ntree=2000, mtry=ncol(train)-1)
x<-varImp(Bag)
setorder(x,Overall)
x
PlotImp<-varImpPlot(Bag, type=2, main = "Bagging")

Random Forest
Ahora se implementa un modelo de RAndom Forest, el cual se calibra mediante validacion cruzada, donde se encuentra que se deben tener en cuenta 2 variables en la creacion de cada árbol. Al ejecutar el modelo calibrado, se encuentra que variables de salario, edad, rol de trabajo, el numero de años que el empleado lleva trabajando y la distancia del trabajo a la casa resultan importantes en la definicion de la satisfacción.
fitControl <- trainControl(method = "cv",number = 10)
RFFit <- train(JobSatisfaction ~ ., data = train, method="rf", trControl = fitControl, tuneLenght=10)
RandomF <- randomForest(JobSatisfaction ~ ., data = train, ntree=2000, mtry=2)
x<-varImp(RandomF)
setorder(x,Overall)
x
PlotImp<-varImpPlot(RandomF, type=2, main = "Random Forest")

Boosting
Por ultimo, se ejecuta un modelo de Boosting, el cual mediante validación cruzada calibra el numero de árboles a utilizar. Este modelo entrega que las variables de Salario diario y mensual, junto con el Rol que el empleado lleva a cabo son determinantes en la satisfacción por el trabajo.
set.seed(123)
HR$BSat<-ifelse(HR$JobSatisfaction==1,1,ifelse(HR$JobSatisfaction==2,2,ifelse(HR$JobSatisfaction==3,3,4)))
HR1<-HR[,-15]
train<-HR1[ID,] #Selecciona el 70% de las filas
test<-HR1[-ID,]
set.seed(123)
Boost<-gbm(BSat~.,data=train,distribution="multinomial",n.trees=2000,interaction.depth=2, cv.folds = 10)
NArboles<-gbm.perf(Boost, method="cv")

set.seed(123)
Boost<-gbm(BSat~.,data=train,distribution="multinomial",n.trees=NArboles,interaction.depth=2)
summary(Boost) #Influencia relativa de cada predictor del modelo

Conclusiones
Al obtener las importancias relativas en cada uno de los modelos se procede a realizar una tablas que condense los reultados y una grafica para una mejor visualizacion. Al pasar el cursor por la grafica se puede observar cada uno de los puntos a que variable equivale, cual es su valor y cual es su modelo (Morado: Boosting, Rosado: CART, Verde: Bagging, Azul: Random Forest)
Results <- read_excel("C:/Users/n.mejia10/Desktop/Prueba/Results.xlsx")
Results
Results2<-reshape2::melt(Results, id.var='Variable')
colnames(Results2)<-c("Variable","Model","Value")
Plt<-ggplot(Results2, aes(x=Variable, y=Value, col=Model, group=1)) + geom_line()+theme(axis.text.x = element_blank(),legend.position="none")
ggplotly(Plt)
En la grafica se puede observar que variables referentes al salario del empleado como: DailyRate, MonthlyRate, HourlyRate, PercentSalaryHike como variables del tipo de trabajo que realiza el empleado como: JobRole, el numero total de años que lleva trabajando (TotalWorkingYears) y la distancia que debe recorrer de la casa al trabajo (DistanceFromHome) resultan de gran importancia a en todos los modelos lo que indica que estas variables son las que mas ayudan a definir la satisfacción de un empleado en su trabajo.
DashBoard
Con esto en mente se desarollo un DashBoard que permite llevar control de variables de Salario, Rol, Balance Trabajo/Vida personal, Porcentaje de Abandono por cada uno de los departamentos de la empresa. El dashboard puede verse en el archivo adjunto app.R
Antes de correr el DashBoard se deben instalar las librerias: Shiny, Shiny DashBoard, scales, ggplot2, ggthemes, dyplr
El dashboard permite seleccionar el departamento a lo que muestra: a) En la parte superior 4 indicadores que son: 1. El porcentaje de abandono para dicho departamento 2. Un promedio de la satisfaccion del ambiente de trabajo 3. Promedio del salario diario que ganan los empleados del departamento 4. Un prmedio del numero de años que llevan los empleados del departamento en la compañia
im<-load.image("C:/Users/n.mejia10/Pictures/Dash1.PNG")
plot(im, axes=FALSE)

- 4 Graficas que relacionan variables importantes con la satisfaccion
- la distribucion de la Satisfaccion por rol de trabajo
- El porcentaje de abandono por nivel de satisfacción en el departamento
- La distribucion del salario diario por nivel de satisfacción
- El nivel de stisfaccion por cada nivel de balance entre la vida personal y el trabajo.
im<-load.image("C:/Users/n.mejia10/Pictures/Dash2.PNG")
plot(im,axes=FALSE)

LS0tDQp0aXRsZTogIlBydWViYSBUZWNuaWNhIg0KYXV0aG9yOiAiTmljb2zhcyBNZWrtYSBNYXJ07W5leiINCmRhdGU6ICJOb3ZpZW1icmUgMjUsIDIwMTgiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IHVuaXRlZA0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gJ0M6L1VzZXJzL24ubWVqaWExMC9EZXNrdG9wL1BydWViYScsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UgKQ0KYGBgDQpTZSBlamVjdXRhbiBsb3MgcGFxdWV0ZXMgbmVjZXNhcmlvczoNCg0KMS4gcmVhZHhsOiBQYXJhIGxlZXIgYXJjaGl2b3MgZGUgZGF0b3MNCjIuIHJwYXJ0OiBQYXJhIGFqdXN0YXIgbW9kZWxvcyBkZSDhcmJvbGVzIGRlIGRlY2lzafNuIA0KMy4gcmFuZG9tRm9yZXN0OiBQYXJhIGVqZWN1dGFyIG1vZGVsb3MgZGUgUmFuZG9tIEZvcmVzdA0KNC4gZ2JtOiBQYXJhIGVqZWN1dGFyIEJvb3N0aW5nIGRlIOFyYm9sZXMNCjUuIGNhcmV0OiBQYXJhIGVudHJlbmFyIG1vZGVsb3MNCjYuIHBzeWNoOiBQYXJhIHVuYSBtZWpvciB2aXN1YWxpemFjafNuIGRlIGxhcyBlc3RhZGlzdGljYXMgZGVzY3JpcHRpdmFzDQo3LiBwbG90bHk6IFBhcmEgZ2VuZXJhciBncmFmaWNvcw0KDQpgYGB7ciB3YXJuaW5nfQ0KbGlicmFyeShyZWFkeGwpDQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShpcHJlZCkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkoZ2JtKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KHBzeWNoKQ0KbGlicmFyeShyZXNoYXBlMikNCmxpYnJhcnkoaW1hZ2VyKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KYGBgDQoNCg0KDQojIE1ldG9kb2xvZ2lhIGRlIGFuYWxpc2lzDQoNCkNvbW8gcHJpbWVyIHBhc28gc2UgZGViZSBpbXBvcnRhciBsb3MgZGF0b3MgYSBsYSBoZXJyYW1pZW50YSBkZSBudWVzdHJhIHByZWZlcmVuY2lhLCBlbiBlc3RlIGNhc28gUiwgY29uIGVsIGZpbiBkZSBmYW1pbGlhcml6YXJzZSBjb24gZWxsb3MgeSByZWFsaXphciB1biBhbmFsaXNpcyBleHBsb3JhdG9yaW8gaW5pY2lhbA0KDQpgYGB7ciBjYXJzfQ0KDQpIUiA8LSByZWFkX2V4Y2VsKCJDOi9Vc2Vycy9uLm1lamlhMTAvRGVza3RvcC9QcnVlYmEvV0FfRm4tVXNlQ18tSFItRW1wbG95ZWUtQXR0cml0aW9uLnhsc3giKSAjI1NlIGltcG9ydGFuIGxvcyBkYXRvcw0KYGBgDQoNCiMjIFRyYXRhbWllbnRvIGRlIHZhcmlhYmxlcw0KRXMgbmVjZXNhcmlvIGVzcGVjaWZpY2FyIGFsIHByb2dyYW1hIGxhIG5hdHVyYWxlemEgZGUgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmxlcyBjb24gZWwgZmluIGRlIG5vIGhhY3IgYW5hbGlzaXMgZXJyb25lb3MuIFBvciBlc3RhIHJhem9uIHZhcmlhYmxlcyBwcmVzZW50ZXMgZW4gbGEgYmFzZSBkZSBkYXRvcyBjb21vOiAiRWR1Y2F0aW9uIiwgIkF0dHJpdGlvbiIsICJHZW5kZXIiIHNlIHRyYW5zZm9ybWFuIGVuIHZhcmlhYmxlcyBjYXRlZ29yaWNhcyBvIGZhY3RvcmVzLg0KDQpgYGB7cn0NCg0KDQpGYWN0b3JlczwtYygiQnVzaW5lc3NUcmF2ZWwiLCAiRWR1Y2F0aW9uIiwgIkVkdWNhdGlvbkZpZWxkIiwgIkVudmlyb25tZW50U2F0aXNmYWN0aW9uIiwgIkdlbmRlciIsICJKb2JJbnZvbHZlbWVudCIsICJKb2JMZXZlbCIsICJKb2JSb2xlIiwgIkpvYlNhdGlzZmFjdGlvbiIsICJNYXJpdGFsU3RhdHVzIiwgIk92ZXIxOCIsICJPdmVyVGltZSIsICJQZXJmb3JtYW5jZVJhdGluZyIsICJSZWxhdGlvbnNoaXBTYXRpc2ZhY3Rpb24iLCAiU3RvY2tPcHRpb25MZXZlbCIsICJXb3JrTGlmZUJhbGFuY2UiLCAiQXR0cml0aW9uIiwgIkRlcGFydG1lbnQiKQ0KDQpIUltGYWN0b3Jlc108LSBsYXBwbHkoSFJbRmFjdG9yZXNdLGZhY3RvcikNCg0KSFI8LUhSWywtYyg5OjEwLCAyMiwgMjcpXQ0KYGBgDQoNCg0KIyMgQW5hbGlzaXMgZXhwbG9yYXRvcmlvIGluaWNpYWwNClNlIHNlbGVjY2lvbmFuIHVuaWNhbWVudGUgbGFzIHZhcmlhYmxlcyBudW1lcmljYXMgeSBzZSBvYnRpZW5lbiBlc3RhZGlzdGljYXMgZGVzY3JpcHRpdmFzIGNvbiBlbCBmaW4gZGUgdmVyIHN1cyB0ZW5kZW5jaWFzIHkgZGlzdHJpYnVjaW9uZXMuDQpgYGB7cn0NCkVEPC1IUiAlPiUgc2VsZWN0KHdoaWNoKHNhcHBseSguLCBpcy5udW1lcmljKSkpDQpkZXNjcmliZShFRCkNCmBgYA0KIFNlIHB1ZWRlIG9ic2VydmFyIHF1ZSBlbCBlbXBsZWFkbyBtZWRpbyBkZSBsYSBjb21wYfFpYSB0aWVuZSAzNiBh8W9zLCBnYW5hIDgwMiBkb2xhcmVzIGRpYXJpb3MsIGhhIHJlY2liaWRvIHVuIHByb21lZGlvIGRlIDMgY2FwYWNpdGFjaW9uZXMgZWwgYfFvIHBhc2FkbywgdGllbmUgMi4xIGHxb3MgZGVzZGUgc3UgdWx0aW1hIHByb21vY2lvbiB5IGxsZXZhIDcgYfFvcyBlbiBsYSBjb21wYfFpYS4NCg0KIyBNb2RlbG9zDQoNClVuYSB2ZXogc2UgcmVhbGl68yB1biBhbmFsaXNpcyBleHBsb3JhdG9yaW8gaW5pY2lhbCwgc2UgcHJvY2VkZSBhIHJlc3BvbmRlciBsYSBwcmVndW50YSBkZSBpbnRlcmVzOiC/Q3VhbGVzIHNvbiBsYXMgdmFyaWFibGVzIHF1ZSBtYXMgaW5mbHV5ZW4gZW4gbGEgc2F0aXNmYWNjafNuIGNvbiByZXNwZWN0byBhbCB0cmFiYWpvIGRlIGxvcyBlbXBsZWFkb3MgZGUgbGEgZW1wcmVzYS4NClBhcmEgcmVzcG9uZGVyIGxhIHByZWd1bnRhIHNlIGVudHJlbmFyYW4gbW9kZWxvcyBiYXNhZG9zIGVuIOFyYm9sZXMgY29uIGVsIGZpIG5kZSBwcmVkZWNpciBsYSB2YXJpYWJsZSBkZSBzYXRpc2ZhY2Np824uIA0KDQpFc3RvIGRlYmlkbyBhIGxhIHZlbnRhamEgcXVlIHBvc2VlbiBsb3Mg4XJib2xlcyBlbiBxdWUgcGVybWl0ZW4sIGRlc3B1ZXMgZGUgZWplY3V0YWRvIGVsIG1vZGVsbywgY3VhbnRpZmljYXIgbGEgaW1wb3J0YW5jaWEgZGUgY2FkYSB2YXJpYWJsZSBlbiBsYSBjb25zdHJ1Y2Npb24gZGVsIG1pc21vLiBFc3RvIG1lZGlhbnRlIGVsIGNvZWZpY2llbnRlIGRlIHB1cmV6YSBvIGdpbmkuDQoNCkVuIGVzdGUgc2VudGlkbyBzZSBhanVzdGFyYW4gMyBtb2RlbG9zIGJhc2FkbyBlbiDhcmJvbGVzOiAgVW4gUmFuZG9tRm9yZXN0LCBVbiBCYWdnaW5nIHkgdW4gQm9vc3RpbmcgY29uIGVsIGZpbiBkZSBlbmNvbnRyYXIgcGFyYSBjYWRhIHVubyBsYXMgdmFyaWFibGVzIGFtcyByZWxldmVuYXRlIHkgY29tcGFyYXIgbG9zIHJlc3VsdGFkb3MuDQoNClBhcmEgcmVhbGl6YXIgbG9zIG1vZGVsb3MsIHNlIHNlcHJhbiBsb3MgZGF0b3MgZW4gMiBzZXRzOiBVbm8gZGUgRW50cmVuYW1pZW50byB5IG90cm8gZGUgVGVzdA0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCklEPC1zYW1wbGUoMTpucm93KEhSKSxzaXplPW5yb3coSFIpKjAuNykNCnRyYWluPC1IUltJRCxdICNTZWxlY2Npb25hIGVsIDcwJSBkZSBsYXMgZmlsYXMNCnRlc3Q8LUhSWy1JRCxdICNTZWxlY2Npb25hIGVsIDMwJSByZXN0YW50ZSBkZSBmaWxhcw0KYGBgDQojIyBDQVJUDQoNCg0KU2UgcmVhbGl6YSB1biDhcmJvbCBkZSBjbGFzaWZpY2FjafNuIGVtIHByaW1lciBsdWdhciBwYXJhIGFwcm92ZWNoYXIgZGUgc3UgZXN0cnVjdHVyYSBncmFmaWNhIHkgZW1wZXphciBjb24gdW5hIGlkZWEgZGUgbGFzIHZhcmG/aWFibGVzIHBvciBsYXMgY3VhbGVzIHBhcnRlIGVuIGxhcyByYW1hcyBzdXBlcmlvcmVzLiBQdWVkZSBvYnNlcnZhcnNlIHF1ZSB2YXJpYWJsZXMgZGUgc2xhcmlvIHkgdGlwbyBkZSB0cmFiYWpvIHJlc3VsdGFuIGxhcyBtYXMgaW1wb3J0YW50ZXMgZW4gbGEgY29uc3RydWNjafNuIGRlbCDhcmJvbC4NCmBgYHtyfQ0KIyMgU2UgYWp1c3RhIHVuIENBUlQgY29uIGxvcyBwYXJhbWV0cm8gYmFzZQ0Kc2V0LnNlZWQoMTIzKQ0KdHJlZSA8LSBycGFydChKb2JTYXRpc2ZhY3Rpb24gfiAuLCBkYXRhID0gdHJhaW4sICBtZXRob2Q9ImNsYXNzIiwgY29udHJvbD1ycGFydC5jb250cm9sKHh2YWw9MTAsIGNwPTFlLTA0KSkNCnBycCh0cmVlKQ0KeDwtdmFySW1wKHRyZWUpDQpzZXRvcmRlcih4LE92ZXJhbGwpDQp4DQoNCmBgYA0KDQojIyBCYWdnaW5nDQoNCkNvbW8gc2lndWllbnRlIHBhc28gc2UgaW1wbGVtZW50YSB1biBCYWdnaW5nIHkgc2UgYnVzY2EgbGEgaW1wb3J0YW5jaWEgZGUgbGFzIHZhcmlhYmxlcy4gQmFnZ2luIHNlIGltcGxlbWVudGEgY29uIGxhIGxpYnJlcmlhIFJhbmRvbUZvcmVzdCBkZWJpZG8gYSBxdWUgZXMgdW4gY2FzbyBlc3BlY2lhbCBkZSBSRiBkb25kZSBzZSB1dGlsaXphbiB0b2RhcyBsYXMgdmFyaWFibGVzIGVuIGNhZGEgbnVldm8g4XJib2wuIEVuIGVzdGUgY2FzbywgcmVzdWx0YW4gaW1wb3J0YW50ZXMgbGFzIHZhcmlhYmxlcyBkZSBzYWxhcmlvLCBzZWd1aWRvIHBvciBsYSBlZGFkIHkgbGEgZGlzdGFuY2lhIHF1ZSBkZWJlIHJlY29ycmVyIGRlIGxhIGNhc2EgYWwgdHJhYmFqby4NCg0KYGBge3IgZmlnMSwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQ0KQmFnIDwtIHJhbmRvbUZvcmVzdChKb2JTYXRpc2ZhY3Rpb24gfiAuLCBkYXRhID0gdHJhaW4sIG50cmVlPTIwMDAsIG10cnk9bmNvbCh0cmFpbiktMSkNCng8LXZhckltcChCYWcpDQpzZXRvcmRlcih4LE92ZXJhbGwpDQp4DQpQbG90SW1wPC12YXJJbXBQbG90KEJhZywgdHlwZT0yLCBtYWluID0gIkJhZ2dpbmciKQ0KDQpgYGANCg0KDQoNCg0KIyNSYW5kb20gRm9yZXN0DQoNCkFob3JhIHNlIGltcGxlbWVudGEgdW4gbW9kZWxvIGRlIFJBbmRvbSBGb3Jlc3QsIGVsIGN1YWwgc2UgY2FsaWJyYSBtZWRpYW50ZSB2YWxpZGFjaW9uIGNydXphZGEsIGRvbmRlIHNlIGVuY3VlbnRyYSBxdWUgc2UgZGViZW4gdGVuZXIgZW4gY3VlbnRhIDIgdmFyaWFibGVzIGVuIGxhIGNyZWFjaW9uIGRlIGNhZGEg4XJib2wuIEFsIGVqZWN1dGFyIGVsIG1vZGVsbyBjYWxpYnJhZG8sIHNlIGVuY3VlbnRyYSBxdWUgdmFyaWFibGVzIGRlIHNhbGFyaW8sIGVkYWQsIHJvbCBkZSB0cmFiYWpvLCBlbCBudW1lcm8gZGUgYfFvcyBxdWUgZWwgZW1wbGVhZG8gbGxldmEgdHJhYmFqYW5kbyB5IGxhIGRpc3RhbmNpYSBkZWwgdHJhYmFqbyBhIGxhIGNhc2EgcmVzdWx0YW4gaW1wb3J0YW50ZXMgZW4gbGEgZGVmaW5pY2lvbiBkZSBsYSBzYXRpc2ZhY2Np824uDQoNCmBgYHtyIGZpZzIsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0NCmZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsbnVtYmVyID0gMTApDQoNClJGRml0IDwtIHRyYWluKEpvYlNhdGlzZmFjdGlvbiB+IC4sIGRhdGEgPSB0cmFpbiwgIG1ldGhvZD0icmYiLCB0ckNvbnRyb2wgPSBmaXRDb250cm9sLCB0dW5lTGVuZ2h0PTEwKQ0KDQpSYW5kb21GIDwtIHJhbmRvbUZvcmVzdChKb2JTYXRpc2ZhY3Rpb24gfiAuLCBkYXRhID0gdHJhaW4sIG50cmVlPTIwMDAsIG10cnk9MikNCng8LXZhckltcChSYW5kb21GKQ0Kc2V0b3JkZXIoeCxPdmVyYWxsKQ0KeA0KUGxvdEltcDwtdmFySW1wUGxvdChSYW5kb21GLCB0eXBlPTIsIG1haW4gPSAiUmFuZG9tIEZvcmVzdCIpDQoNCmBgYA0KIyMgQm9vc3RpbmcNCg0KUG9yIHVsdGltbywgc2UgZWplY3V0YSB1biBtb2RlbG8gZGUgQm9vc3RpbmcsIGVsIGN1YWwgbWVkaWFudGUgdmFsaWRhY2nzbiBjcnV6YWRhIGNhbGlicmEgZWwgbnVtZXJvIGRlIOFyYm9sZXMgYSB1dGlsaXphci4gRXN0ZSBtb2RlbG8gZW50cmVnYSBxdWUgbGFzIHZhcmlhYmxlcyBkZSBTYWxhcmlvIGRpYXJpbyB5IG1lbnN1YWwsIGp1bnRvIGNvbiBlbCBSb2wgcXVlIGVsIGVtcGxlYWRvIGxsZXZhIGEgY2FibyBzb24gZGV0ZXJtaW5hbnRlcyBlbiBsYSBzYXRpc2ZhY2Np824gcG9yIGVsIHRyYWJham8uDQpgYGB7cn0NCg0Kc2V0LnNlZWQoMTIzKQ0KSFIkQlNhdDwtaWZlbHNlKEhSJEpvYlNhdGlzZmFjdGlvbj09MSwxLGlmZWxzZShIUiRKb2JTYXRpc2ZhY3Rpb249PTIsMixpZmVsc2UoSFIkSm9iU2F0aXNmYWN0aW9uPT0zLDMsNCkpKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpIUjE8LUhSWywtMTVdDQp0cmFpbjwtSFIxW0lELF0gI1NlbGVjY2lvbmEgZWwgNzAlIGRlIGxhcyBmaWxhcw0KdGVzdDwtSFIxWy1JRCxdDQoNCg0Kc2V0LnNlZWQoMTIzKQ0KQm9vc3Q8LWdibShCU2F0fi4sZGF0YT10cmFpbixkaXN0cmlidXRpb249Im11bHRpbm9taWFsIixuLnRyZWVzPTIwMDAsaW50ZXJhY3Rpb24uZGVwdGg9MiwgY3YuZm9sZHMgPSAxMCkNCg0KTkFyYm9sZXM8LWdibS5wZXJmKEJvb3N0LCBtZXRob2Q9ImN2IikgIA0KDQpzZXQuc2VlZCgxMjMpDQpCb29zdDwtZ2JtKEJTYXR+LixkYXRhPXRyYWluLGRpc3RyaWJ1dGlvbj0ibXVsdGlub21pYWwiLG4udHJlZXM9TkFyYm9sZXMsaW50ZXJhY3Rpb24uZGVwdGg9MikNCnN1bW1hcnkoQm9vc3QpICNJbmZsdWVuY2lhIHJlbGF0aXZhIGRlIGNhZGEgcHJlZGljdG9yIGRlbCBtb2RlbG8NCg0KYGBgDQoNCiMgQ29uY2x1c2lvbmVzDQpBbCBvYnRlbmVyIGxhcyBpbXBvcnRhbmNpYXMgcmVsYXRpdmFzIGVuIGNhZGEgdW5vIGRlIGxvcyBtb2RlbG9zIHNlIHByb2NlZGUgYSByZWFsaXphciB1bmEgdGFibGFzIHF1ZSBjb25kZW5zZSBsb3MgcmV1bHRhZG9zIHkgdW5hIGdyYWZpY2EgcGFyYSB1bmEgbWVqb3IgdmlzdWFsaXphY2lvbi4gQWwgcGFzYXIgZWwgY3Vyc29yIHBvciBsYSBncmFmaWNhIHNlIHB1ZWRlIG9ic2VydmFyIGNhZGEgdW5vIGRlIGxvcyBwdW50b3MgYSBxdWUgdmFyaWFibGUgZXF1aXZhbGUsIGN1YWwgZXMgc3UgdmFsb3IgeSBjdWFsIGVzIHN1IG1vZGVsbyAoTW9yYWRvOiBCb29zdGluZywgUm9zYWRvOiBDQVJULCBWZXJkZTogQmFnZ2luZywgQXp1bDogUmFuZG9tIEZvcmVzdCkNCg0KYGBge3IgZmlnMywgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9N30NClJlc3VsdHMgPC0gcmVhZF9leGNlbCgiQzovVXNlcnMvbi5tZWppYTEwL0Rlc2t0b3AvUHJ1ZWJhL1Jlc3VsdHMueGxzeCIpDQpSZXN1bHRzDQoNClJlc3VsdHMyPC1yZXNoYXBlMjo6bWVsdChSZXN1bHRzLCBpZC52YXI9J1ZhcmlhYmxlJykNCmNvbG5hbWVzKFJlc3VsdHMyKTwtYygiVmFyaWFibGUiLCJNb2RlbCIsIlZhbHVlIikNCg0KUGx0PC1nZ3Bsb3QoUmVzdWx0czIsIGFlcyh4PVZhcmlhYmxlLCB5PVZhbHVlLCBjb2w9TW9kZWwsIGdyb3VwPTEpKSArIGdlb21fbGluZSgpK3RoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpDQoNCmdncGxvdGx5KFBsdCkNCg0KDQpgYGANCg0KRW4gbGEgZ3JhZmljYSBzZSBwdWVkZSBvYnNlcnZhciBxdWUgdmFyaWFibGVzIHJlZmVyZW50ZXMgYWwgc2FsYXJpbyBkZWwgZW1wbGVhZG8gY29tbzogRGFpbHlSYXRlLCBNb250aGx5UmF0ZSwgSG91cmx5UmF0ZSwgUGVyY2VudFNhbGFyeUhpa2UgY29tbyB2YXJpYWJsZXMgZGVsIHRpcG8gZGUgdHJhYmFqbyBxdWUgcmVhbGl6YSBlbCBlbXBsZWFkbyBjb21vOiBKb2JSb2xlLCBlbCBudW1lcm8gdG90YWwgZGUgYfFvcyBxdWUgbGxldmEgdHJhYmFqYW5kbyAoVG90YWxXb3JraW5nWWVhcnMpIHkgbGEgZGlzdGFuY2lhIHF1ZSBkZWJlIHJlY29ycmVyIGRlIGxhIGNhc2EgYWwgdHJhYmFqbyAoRGlzdGFuY2VGcm9tSG9tZSkgIHJlc3VsdGFuIGRlIGdyYW4gaW1wb3J0YW5jaWEgYSBlbiB0b2RvcyBsb3MgbW9kZWxvcyBsbyBxdWUgaW5kaWNhIHF1ZSBlc3RhcyB2YXJpYWJsZXMgc29uIGxhcyBxdWUgbWFzIGF5dWRhbiBhIGRlZmluaXIgbGEgc2F0aXNmYWNjafNuIGRlIHVuIGVtcGxlYWRvIGVuIHN1IHRyYWJham8uDQoNCiMgRGFzaEJvYXJkDQoNCkNvbiBlc3RvIGVuIG1lbnRlIHNlIGRlc2Fyb2xsbyB1biBEYXNoQm9hcmQgcXVlIHBlcm1pdGUgbGxldmFyIGNvbnRyb2wgZGUgdmFyaWFibGVzIGRlIFNhbGFyaW8sIFJvbCwgQmFsYW5jZSBUcmFiYWpvL1ZpZGEgcGVyc29uYWwsIFBvcmNlbnRhamUgZGUgQWJhbmRvbm8gcG9yIGNhZGEgdW5vIGRlIGxvcyBkZXBhcnRhbWVudG9zIGRlIGxhIGVtcHJlc2EuIEVsIGRhc2hib2FyZCBwdWVkZSB2ZXJzZSBlbiBlbCBhcmNoaXZvIGFkanVudG8gYXBwLlINCg0KQW50ZXMgZGUgY29ycmVyIGVsIERhc2hCb2FyZCBzZSBkZWJlbiBpbnN0YWxhciBsYXMgbGlicmVyaWFzOiBTaGlueSwgU2hpbnkgIERhc2hCb2FyZCwgc2NhbGVzLCBnZ3Bsb3QyLCBnZ3RoZW1lcywgZHlwbHINCg0KRWwgZGFzaGJvYXJkIHBlcm1pdGUgc2VsZWNjaW9uYXIgZWwgZGVwYXJ0YW1lbnRvIGEgbG8gcXVlIG11ZXN0cmE6IA0KYSkgRW4gbGEgcGFydGUgc3VwZXJpb3IgNCBpbmRpY2Fkb3JlcyBxdWUgc29uOiANCiAgMS4gRWwgcG9yY2VudGFqZSBkZSBhYmFuZG9ubyBwYXJhIGRpY2hvIGRlcGFydGFtZW50bw0KICAyLiBVbiAgcHJvbWVkaW8gZGUgbGEgc2F0aXNmYWNjaW9uIGRlbCBhbWJpZW50ZSBkZSB0cmFiYWpvDQogIDMuIFByb21lZGlvIGRlbCBzYWxhcmlvIGRpYXJpbyBxdWUgZ2FuYW4gbG9zIGVtcGxlYWRvcyBkZWwgZGVwYXJ0YW1lbnRvDQogIDQuIFVuIHBybWVkaW8gZGVsIG51bWVybyBkZSBh8W9zIHF1ZSBsbGV2YW4gbG9zIGVtcGxlYWRvcyBkZWwgZGVwYXJ0YW1lbnRvIGVuIGxhIGNvbXBh8WlhDQogIA0KICANCiAgDQogIA0KYGBge3J9DQppbTwtbG9hZC5pbWFnZSgiQzovVXNlcnMvbi5tZWppYTEwL1BpY3R1cmVzL0Rhc2gxLlBORyIpDQpwbG90KGltLCBheGVzPUZBTFNFKQ0KDQpgYGANCg0KYikgNCBHcmFmaWNhcyBxdWUgcmVsYWNpb25hbiB2YXJpYWJsZXMgaW1wb3J0YW50ZXMgY29uIGxhIHNhdGlzZmFjY2lvbg0KICAxLiBsYSBkaXN0cmlidWNpb24gZGUgbGEgU2F0aXNmYWNjaW9uIHBvciByb2wgZGUgdHJhYmFqbw0KICAyLiBFbCBwb3JjZW50YWplIGRlIGFiYW5kb25vIHBvciBuaXZlbCBkZSBzYXRpc2ZhY2Np824gZW4gZWwgZGVwYXJ0YW1lbnRvDQogIDMuIExhIGRpc3RyaWJ1Y2lvbiBkZWwgc2FsYXJpbyBkaWFyaW8gcG9yIG5pdmVsIGRlIHNhdGlzZmFjY2nzbg0KICA0LiBFbCBuaXZlbCBkZSBzdGlzZmFjY2lvbiBwb3IgY2FkYSBuaXZlbCBkZSBiYWxhbmNlIGVudHJlIGxhIHZpZGEgcGVyc29uYWwgeSBlbCB0cmFiYWpvLg0KDQpgYGB7cn0NCmltPC1sb2FkLmltYWdlKCJDOi9Vc2Vycy9uLm1lamlhMTAvUGljdHVyZXMvRGFzaDIuUE5HIikNCnBsb3QoaW0sYXhlcz1GQUxTRSkNCg0KYGBgDQoNCg==